package jee.boot.module.oauth2.auth;

import jee.boot.common.basic.result.MapData;
import jee.boot.common.basic.result.MessageCodeHelper;
import jee.boot.entity.auth.Account;
import jee.boot.common.properties.ProjectConfig;
import jee.boot.common.remote.RemoteEndpoint;
import jee.boot.common.utils.SpringContextHolder;
import jee.boot.common.utils.StringUtils;
import jee.boot.module.oauth2.auth.verification.AccountValidator;
import jee.boot.module.oauth2.auth.verification.AccountVerificationContext;
import jee.boot.module.oauth2.auth.verification.AccountVerificationException;
import jee.boot.module.oauth2.auth.verification.AccountVerificationFilter;
import jee.boot.module.oauth2.config.UserDetailHandler;
import jee.boot.module.oauth2.service.AccountService;
import jee.boot.entity.auth.AuthConstant;
import jee.boot.entity.system.SystemConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.util.DigestUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 *
 */
public class CustomPasswordTokenGranter extends AbstractTokenGranter {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private static final String GRANT_TYPE = "password";
    private int DEFAULT_LOCK_TIMES = 5;//默认密码错误次数锁定
    private final AuthenticationManager authenticationManager;
    private RedisTemplate<String, Object> redisTemplate;

    public CustomPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, RedisTemplate<String, Object> redisTemplate) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, redisTemplate, GRANT_TYPE);
    }

    protected CustomPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, RedisTemplate<String, Object> redisTemplate, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        UserDetailHandler userDetailHandler = SpringContextHolder.getBean(UserDetailHandler.class);
        AccountService accountService = SpringContextHolder.getBean(AccountService.class);
        AccountValidator accountValidator = SpringContextHolder.getApplicationContext().getBean(AccountValidator.class);
        RemoteEndpoint remoteEndpoint = SpringContextHolder.getApplicationContext().getBean(RemoteEndpoint.class);
        ProjectConfig projectConfig = SpringContextHolder.getApplicationContext().getBean(ProjectConfig.class);
        // 获取入参用户名/密码/验证码
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String username = parameters.get("username");
        String password = parameters.get("password");
        String passwdType = parameters.get("passwdType");
        /**
         * 组装上下文的数据
         */
        AccountVerificationContext context = new AccountVerificationContext();
        // 设置请求参数
        context.setTokenRequest(tokenRequest);
        // 设置登录用户
        UserDetails userDetails = userDetailHandler.loadUserByUsername(username);
        if (userDetails == null) {
            throw new AccountVerificationException(getMessageCodeHelper().getMessage(1114));
        }
        Account account = (Account) userDetails;
        account.setPassword(null);
        if (account.getPasswordErrors() == null) {
            account.setPasswordErrors(0);
        }
        context.setAccount(account);
//        Map<String, SystemConfig> configServiceAsMap = systemConfigService.findAsMap(new SystemConfig());
        // 设置账户配置信息
//        context.setAccountConfigMap(configServiceAsMap);

        // 认证前的数据校验
        List<AccountVerificationFilter> beforeAuthenticateFilters = accountValidator.getBeforeAuthenticateFilters();
        if (beforeAuthenticateFilters != null && !beforeAuthenticateFilters.isEmpty()) {
            for (AccountVerificationFilter filter : beforeAuthenticateFilters) {
                filter.verification(context);
            }
        }
        if (StringUtils.isNotBlank(passwdType)) {
            switch (passwdType) {
                // 动态密码(包括短信验证码/邮件验证码)
                case AuthConstant.PASSWD_TYPE_DYNAMIC:
                    String dynamicKey = parameters.get("dynamicKey");
                    String dynamicCode = (String) redisTemplate.opsForValue().get(dynamicKey);
                    if (StringUtils.isNotBlank(dynamicCode)) {
                        //check 之后为了安全需要删除redis里面的 code,不能再这里删除,因为这个方法会被重复调用,
                        redisTemplate.delete(dynamicKey);
                        // 如果是动态密码登录,则使用动态码作为密码
                        password = DigestUtils.md5DigestAsHex(dynamicCode.getBytes());
                    } else {
                        password = null;
                    }
                    break;
                // 静态密码,保持数据库原有的既可
                case AuthConstant.PASSWD_TYPE_STATIC:
                default:
                    break;
            }
        }
        // 进行验证用户名密码
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        SystemConfig query = new SystemConfig();
        query.setCode("LOCK_TIMES");
        SystemConfig config = remoteEndpoint.executePost(projectConfig.getOpenSystemServer(), "/config/getByCode", query, SystemConfig.class);
        int lockTimesInt = config.getCode() == null ? DEFAULT_LOCK_TIMES : Integer.parseInt(config.getValue());
        try {
            userAuth = authenticationManager.authenticate(userAuth);

            // 设置错误密码锁定账户功能如果
            if (lockTimesInt > 0) {
                // 如果验证成功,需要更新错误的记录入为0
                if (account.getPasswordErrors() > 0) {
                    account.setPasswordErrors(0);
                    accountService.update(account);
                }
            }
        } catch (AccountStatusException ase) {
            logger.error(ase.getMessage(), ase);
            throw ase;
        } catch (BadCredentialsException e) {
            // 设置错误密码锁定账户功能如果
            if (lockTimesInt > 0) {
                account.setPasswordErrors(account.getPasswordErrors() + 1);
                // 如果错误次数超过了设定值,需要锁定账户
                if (account.getPasswordErrors() >= lockTimesInt) {
                    account.setAccountStatus("account_status_2");//锁定
                }
                accountService.update(account);
            }
            throw new AccountVerificationException(getMessageCodeHelper().getMessage(1109, MapData.instance().put("errors", account.getPasswordErrors()).put("times", lockTimesInt).data(), null));
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new AccountVerificationException(getMessageCodeHelper().getMessage(500));
        }
        // 认证后的数据校验
        List<AccountVerificationFilter> afterAuthenticateFilters = accountValidator.getAfterAuthenticateFilters();
        if (beforeAuthenticateFilters != null && !afterAuthenticateFilters.isEmpty()) {
            for (AccountVerificationFilter filter : afterAuthenticateFilters) {
                filter.verification(context);
            }
        }
        // 正常返回
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

    public MessageCodeHelper getMessageCodeHelper() {
        return SpringContextHolder.getApplicationContext().getBean(MessageCodeHelper.class);
    }
}
